/*********************************************************************
 * URLJoinHandler.java created on Feb 5, 2010
 *
 * This file is part of Sure Tech Support Portal System. 
 * Copyright @ 2010 Sure Tech (HK) Limited. All rights reserved.
 * Sure Tech PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *********************************************************************/
package com.bizdigger.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.StringTokenizer;

/**
 * @Author: Mac Wang
 * @Date: Feb 5, 2010 2:29:59 PM 
 * @Email: mac@suretech.com.hk
 */

/**
 * A simple URL stream handler that "joins" a list of URLs in the sense that
 * reading data from the resulting URL will read data from all of them sequentially.
 * Not a particularly useful URL scheme, but it is simple enough to implement
 * as a example. It allows creation of URLs in the format
 * <code>urljoin:url_1,url_2,...</code>, with any number of nested URLs separated
 * with commas (which means this won't work when the URLs themselves contain
 * commas).<p>
 * 
 * This stream handler does not supports creating URLs from base and relative
 * parts (the base part will always override the relative part).<p>
 * 
 * To plug this scheme in, add "urljoin = URLJoinHandler" mapping
 * to a {@link com.vladium.utils.URLFactory#HANDLER_MAPPING_RESOURCE_NAME}
 * resource in the classpath.
 * 
 * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
 */
public class URLJoinHandler extends URLStreamHandler {
	public static final String PROTOCOL = "urljoin";
	
	protected URLConnection openConnection (final URL url) throws IOException
    {
        return new URLJoinURLConnection (url);
    }
    
    protected String toExternalForm (final URL url)
    {
        return PROTOCOL.concat (":").concat (url.getFile ());
    }
    
    protected void parseURL (final URL context, final String spec, final int start, final int limit)
    {
        final String opaquePart = context.getFile () != null ? context.getFile () : spec.substring (start);
        
        setURL (context, context.getProtocol (), "", -1, opaquePart, "");
    }
    
    /*
     * The URLConnection implementation used by this scheme. Is uses nested
     * JoinInputStream class to join streams.  
     */    
    private static final class URLJoinURLConnection extends URLConnection
    {
        public synchronized void connect () throws IOException
        {
            final String opaquePart = url.getFile ();
            final StringTokenizer tokenizer = new StringTokenizer (opaquePart, ",");
            final URL [] nestedURLs = new URL [tokenizer.countTokens ()];
            
            for (int i = 0; tokenizer.hasMoreTokens (); ++ i)
            {
                nestedURLs [i] = URLFactory.newURL (tokenizer.nextToken ());
            }
            
            m_in = new JoinInputStream (nestedURLs);
        }
        
        public synchronized InputStream getInputStream () throws IOException
        {
            connect ();
            return m_in; 
        }

        protected URLJoinURLConnection (final URL url)
        {
            super (url);
        }
        
        private JoinInputStream m_in;

    }

    
    /*
     * A simple InputStream implementation for concatenating streams from several URLs. This class is not thread safe. 
     */
    private static final class JoinInputStream extends InputStream
    {
        public int read () throws IOException
        {
            if (m_in == null) 
            	return -1; // all URLs drained
            
            int _byte = m_in.read ();
            
            while ((_byte < 0) && (m_stream < m_urls.length - 1))
            {
                m_in.close ();
                m_in = m_urls [++ m_stream].openStream ();
                
                _byte = m_in.read ();
            }
            
            if (_byte < 0)
            {
                m_in = null;
                return -1;
            }
            
            return _byte;
        }
        
        public void close () throws IOException
        {
            if (m_in != null) 
            	m_in.close ();
        }

        JoinInputStream (final URL [] urls)throws IOException
        {
            m_urls = urls;
            
            m_in = urls [0].openStream ();
            m_stream = 0;
        }
        
        
        private final URL [] m_urls;
        private InputStream m_in;
        private int m_stream; 
    }
}
